package com.lipaluma.field.mapper.multiple;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.lang.StringUtils;

import com.lipaluma.context.Context;
import com.lipaluma.field.mapper.AbstractFieldMapper;
import com.lipaluma.field.mapper.FieldAdaptedOnEachValue;
import com.lipaluma.field.mapper.FieldValidated;
import com.lipaluma.field.mapper.initializer.FieldMapperInitializer;
import com.lipaluma.field.mapper.initializer.XmlFieldMapperInitializer;
import com.lipaluma.field.mapper.mapping.FieldMappingInfo;
import com.lipaluma.field.mapper.mapping.MappingOptions;
import com.lipaluma.io.Row;
import com.lipaluma.parser.marshalling.FieldMarshalled;
import com.lipaluma.parser.marshalling.ObjectMarshalled;
import com.lipaluma.xml.mapping.XmlFieldMapping;


public abstract class AbstractMultipleFieldMapper extends AbstractFieldMapper implements MultipleFieldMapper, FieldMapperInitializer, XmlFieldMapperInitializer {
	protected List<? extends FieldMappingInfo> mappings = new ArrayList<FieldMappingInfo>();	
	protected boolean keepAll;

	/**
	 * Create the field mapper without initialization.
	 */
	public AbstractMultipleFieldMapper() {}
	
	/**
	 * Initialize a field mapper used to map on one field several values from the source
	 * @param mappings information of all mappings
	 */
	public AbstractMultipleFieldMapper(List<FieldMappingInfo> mappings) {
		this(mappings, false);
	}

	/**
	 * Initialize a field mapper used to map on one field several values from the source.
	 * @param mappings information of all mappings
	 * @param keepAll allows to keep all value of each mapping even if the value is null or empty.
	 */
	public AbstractMultipleFieldMapper(List<FieldMappingInfo> mappings, boolean keepAll) {
		if(mappings == null || mappings.isEmpty())
			throw new IllegalArgumentException("Error in constructor of class "+this.getClass().getSimpleName()+" : the mappings cannot be empty or null");
		this.mappings = new ArrayList<FieldMappingInfo>(mappings);
		this.keepAll = keepAll;
	}
	
//	public void processHeader(String columnName, int columnIndex) {
////		if(columnIndexes == null)
////			columnIndexes = new Integer[columnNames.length];
//		for (FieldMappingInfo mapping : mappings) {
//			Column column = (Column)mapping;
//			if(column.getColumnName().equalsIgnoreCase(columnName)) {
//				column.mapIndexByColumnName(columnName, columnIndex);
//				return;
//			}
//		}
//	}
//
//	public void writeHeader(Row row) {
//		for (FieldMappingInfo mapping : mappings) {
//			Column column = (Column)mapping;
//			row.writeValue(column.getColumnName(), mapping);
//		}
////		for (int i = 0; i < columnNames.length; i++) {
////			row.writeValueOnRow(columnNames[i], columnIndexes[i]);
////		}
//	}
	
	@Override
	public void init(Context context, Field field) {
		this.field = field;
		if(mappings == null || mappings.isEmpty())
			mappings = context.getBasicMappingManager().initMultipleMappings(field);
		MappingOptions options = context.getBasicMappingManager().initOptions(field);
		if(options != null)
			init(options);
		if(FieldAdaptedOnEachValue.class.isAssignableFrom(this.getClass()))
			((FieldAdaptedOnEachValue)this).initAdaptorOnEachValue(context, field);
		if(FieldValidated.class.isAssignableFrom(this.getClass()))
			((FieldValidated)this).initValidator(context, field);
	}

	@Override
	public void init(Context context, Field field, XmlFieldMapping mapping) {
		this.field = field;
		mappings = context.getMappingManagerByXml().initMultipleMappings(field, mapping);
		init(MappingOptions.builder().by(mapping).build());
		if(FieldAdaptedOnEachValue.class.isAssignableFrom(this.getClass()))
			((FieldAdaptedOnEachValue)this).initAdaptorOnEachValue(context, field, mapping);
		if(FieldValidated.class.isAssignableFrom(this.getClass()))
			((FieldValidated)this).initValidator(context, field, mapping);
	}
	
	protected abstract void init(MappingOptions mappingOptions);

	@Override
	public Object unmarshal(Row row) {
		List<String> extractedValues = extractValuesOnList(row);
		if(FieldAdaptedOnEachValue.class.isAssignableFrom(this.getClass())) {
			extractedValues = ((FieldAdaptedOnEachValue)this).adaptEachValues(extractedValues);
		}
		if(!keepAll)
			removeEmptyValues(extractedValues);		
		Object valueParsed = parseExtractedValues(extractedValues);

		if(FieldValidated.class.isAssignableFrom(this.getClass())) {
			((FieldValidated)this).validateValue(valueParsed);
		}
		return valueParsed;
	}

	private List<String> extractValuesOnList(Row row) {
		List<String> values = new ArrayList<String>();
		for (FieldMappingInfo mapping : mappings) {
			values.add(row.extractValue(mapping));
		}
		return values;
	}
	
	private List<String> removeEmptyValues(List<String> extractedValues) {
		List<String> values = new ArrayList<String>();
		for (String value : extractedValues) {
			if(StringUtils.isNotEmpty(value))
				values.add(value);
		}
		return values;
	}

	protected abstract Object parseExtractedValues(List<String> values);
		
	public void writeValueOnRow(Object value, Row row) {
		List<String> values = formatValue(value);
		for (int i = 0; i < mappings.size(); i++) {
			if(values != null && i < values.size())
				row.writeValue(values.get(i), mappings.get(i));
			else
				row.writeValue(null, mappings.get(i));					
		}
	}

	@Override
	public void marshalValue(Object value, ObjectMarshalled marshal) {
		List<String> values = formatValue(value);
		for (int i = 0; i < mappings.size(); i++) {
			if(values != null && i < values.size())
				marshal.addFieldMarshalled(new FieldMarshalled(field, mappings.get(i), values.get(i)));
//				row.writeValue(values.get(i), mappings.get(i));
//			else
//				row.writeValue(null, mappings.get(i));					
		}
	}
	
	protected abstract List<String> formatValue(Object value);
	
	@Override
	public FieldMappingInfo[] getMappings() {
		return mappings.toArray(new FieldMappingInfo[0]);
	}
}
